/** 蓝牙服务 */
class SmartPenBluetooth {
  /** 在线笔迹服务 */
  static UUID_ONLINE = "0000fff0-0000-1000-8000-00805f9b34fb"
  /** 在线笔迹服务 通知特征值 */
  static UUID_ONLINE_NOTIFY = "0000fff1-0000-1000-8000-00805f9b34fb"

  /** 离线笔迹服务 */
  static UUID_OFFLINE = "0000f200-0000-1000-8000-00805f9b34fb"
  /** 离线笔迹服务 通知特征值 */
  static UUID_OFFLINE_NOTIFY = "0000f202-0000-1000-8000-00805f9b34fb"

  /** 笔参数服务 */
  static UUID_PARAMETER = "0000f100-0000-1000-8000-00805f9b34fb"
  /** 笔参数服务 写入特征值 */
  static UUID_PARAMETER_WRITE = "0000f102-0000-1000-8000-00805f9b34fb"
  /** 笔参数服务  通知特征值 */
  static UUID_PARAMETER_NOTIFY = "0000f101-0000-1000-8000-00805f9b34fb"

  /** OTA升级服务 (111/301) */
  static UUID_UPGRADE = "9e5d1e47-5c13-43a0-8635-82ad38a1386f"
  /** OTA升级服务 写入特征值 */
  static UUID_UPGRADE_CONTROL_POINT = "e3dd50bf-f7a7-4e99-838e-570a086c666b"
  /** OTA升级服务 写入特征值 */
  static UUID_UPGRADE_DATA = "92e86c7a-d961-4091-b74f-2409e72efe36"
  /** OTA升级服务 写入特征值 */
  static UUID_UPGRADE_APP_INFO = "347f7608-2e2d-47eb-913b-75d4edc4de3b"
  /** OTA升级服务 写入特征值 */
  static UUID_CLIENT_CONFIGURATION = "00002902-0000-1000-8000-00805f9b34fb"
}

function to(promise) {
  return new Promise((resolve) => {
    promise.then((result) => {
      resolve([null, result])
    }).catch((error) => {
      resolve([error, null])
    })
  })
}

/**
 * WebBluetooth
 * 请求设备
 * https://webbluetoothcg.github.io/web-bluetooth
 */
function writeValueWithResponse(characteristic, value) {
  return to(characteristic.writeValueWithResponse(new DataView(new Uint8Array(value).buffer)))
}

/** 延迟 */
function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time))
}

/** bytes转字符串 */
function byteToString(arr) {
  if (typeof arr === 'string') {
    return arr;
  }
  var str = '',
    _arr = arr;
  for (var i = 0; i < _arr.length; i++) {
    var one = _arr[i].toString(2),
      v = one.match(/^1+?(?=0)/);
    if (v && one.length == 8) {
      var bytesLength = v[0].length;
      var store = _arr[i].toString(2).slice(7 - bytesLength);
      for (var st = 1; st < bytesLength; st++) {
        store += _arr[st + i].toString(2).slice(2);
      }
      str += String.fromCharCode(parseInt(store, 2));
      i += bytesLength - 1;
    } else {
      str += String.fromCharCode(_arr[i]);
    }
  }
  return str;
}

/** 获取文件bytes */
function getFileBytes(file) {
  return to(
    new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.readAsArrayBuffer(file)
      reader.onload = () => resolve([...new Uint8Array(reader.result)])
      reader.onerror = (error) => reject(error)
    })
  )
}

/** 十进制转十六进制 */
function decToHex(dec) {
  return dec >= -15 && dec <= 15 ? dec.toString(16).padStart(2, "0") : dec.toString(16)
}

/** 十六进制转十进制 */
function hexToDec(hex) {
  return parseInt(hex, 16)
}

/** 获取文件bytes */
function getOTAFileFormat(bytes) {
  /** 文件标识 固定16进制 54514C 对应字符串为TQL */
  const flag = byteToString(bytes.slice(0, 3))
  /** 
   * 文件类型 
   * 0:MCU+BT
   * 1:MCU
   * 2:BT
   */
  const fileType = bytes.slice(3, 4)[0]
  /** 笔型号 */
  const model = bytes.slice(4, 5)[0]

  const getVersionString = (bytes) => byteToString(bytes.filter(i => i !== 0))
  const getVersionLength = (bytes) => hexToDec(bytes.reverse().map(dec => decToHex(dec)).join(""))
  /** 蓝牙版本号 */
  const btVersion = getVersionString(bytes.slice(5, 15))
  /** 蓝牙数据长度Bytes */
  const btLengthBytes = bytes.slice(15, 19)
  /** 蓝牙数据长度 */
  const btLength = getVersionLength(bytes.slice(15, 19))
  /** MCU版本号 */
  const mcuVersion = getVersionString(bytes.slice(19, 29))
  /** MCU数据长度Bytes */
  const mcuLengthBytes = bytes.slice(29, 33)
  /** MCU数据长度 */
  const mcuLength = getVersionLength(bytes.slice(29, 33))
  /** TODO MD5校验值 */
  const md5 = bytes.slice(33, 49)

  let btBytes, mcuBytes
  switch (fileType) {
    case 0:

      break;
    case 1:

      break;
    case 2:
      btBytes = bytes.slice(49, 49 + btLength)
      break;

  }

  return {
    flag,
    fileType,
    model,
    btVersion,
    btLengthBytes,
    btBytes,
    mcuVersion,
    mcuLength,
    mcuLengthBytes,
    mcuBytes,
    md5
  }
}

class Bluetooth {
  /** TODO 是否连接 */
  isConnected = false

  /** TODO 是否支持 */
  isSupported = false

  /** 当前设备 */
  device = null

  /** 当前设备特征值 */
  characteristics = null

  /** 连接 */
  async connect() {
    return new Promise(async (resolve, reject) => {
      // 请求设备
      const [requestDeviceError, device] = await to(
        navigator.bluetooth.requestDevice({
          filters: [{
            manufacturerData: [{
              companyIdentifier: 0x3231,
            }]
          }],
          optionalServices: [
            SmartPenBluetooth.UUID_ONLINE,
            SmartPenBluetooth.UUID_OFFLINE,
            SmartPenBluetooth.UUID_PARAMETER,
            SmartPenBluetooth.UUID_UPGRADE
          ],
        })
      )
      if (requestDeviceError) return reject(requestDeviceError)
      console.log('Device', device)

      this.device = device

      // 连接
      const [connectError, server] = await to(device.gatt.connect())
      if (connectError) return reject('连接失败')
      console.log('Server', server)

      // 获取服务
      const [getPrimaryServicesError, services] = await to(server.getPrimaryServices())
      if (getPrimaryServicesError) return reject('连接失败')
      console.log('Service', services)

      // 获取所有特征值
      const [getAllCharacteristicsError, characteristics] = await to(
        Promise.all(
          services.map(
            i => i.getCharacteristics()
          )
        ).then(result => result.flat())
      )
      if (getAllCharacteristicsError) return reject('连接失败')
      console.log('Characteristics', characteristics)

      // 存储特征值
      this.characteristics = new Map(characteristics.map(characteristic => [characteristic.uuid, characteristic]))

      resolve('连接成功')
    })

  }

  /** 
   * 断开 
   */
  disconnect() {
    this.device.gatt.disconnect()
    this.device = null
  }

  /**
   * 获取模块版本信息
   */
  async getModuleVersion() {
    const [error, result] = await to(this.characteristics.get(SmartPenBluetooth.UUID_UPGRADE_APP_INFO).readValue())
    if (error) return Promise.reject(error)
    return Promise.resolve(byteToString(new Uint8Array(result.buffer).filter(byte => byte !== 0)))
  }

  /**
   * OTA升级
   */
  async upgrade(file, progressCallback) {
    // 特征值
    const characteristic1 = this.characteristics.get(SmartPenBluetooth.UUID_UPGRADE_CONTROL_POINT)
    // 开启通知
    characteristic1.startNotifications()
    const characteristic2 = this.characteristics.get(SmartPenBluetooth.UUID_UPGRADE_DATA)

    // 封装步骤
    const characteristic1Request = (value) => to(
      new Promise(async (resolve, reject) => {
        const listener = () => {
          const response = new Uint8Array(characteristic1.value.buffer)[0]
          response === 0 ? resolve() : reject("升级失败")
          characteristic1.removeEventListener('characteristicvaluechanged', listener)
        }
        characteristic1.addEventListener('characteristicvaluechanged', listener)
        const [error] = await writeValueWithResponse(characteristic1, value)
        if (error) return reject()
      })
    )

    return new Promise(async (resolve, reject) => {

      const [error, bytes] = await getFileBytes(file)
      if (error) return reject("升级失败")
      const data = getOTAFileFormat(bytes)

      /**
       * 第一步
       * 通过 characteristic1 写入 0x01 + OTA文件大小bytes(高低位转换)
       * 例：文件大小为0xAABBCCDD 写入的byte数组为：[0x01,0xDD,0xCC,0xBB,0xAA]，返回0成功，其他值均为失败。
       */
      const [firstStepError] = await characteristic1Request([0x01, ...data.btLengthBytes.reverse()])
      if (firstStepError) return reject("升级失败")

      /**
       * 第二步
       * 通过 characteristic1 写入 0x02
       * 返回0成功，其他值均为失败。
       */
      const [secondStepError] = await characteristic1Request([0x02])
      if (secondStepError) return reject("升级失败")

      /**
       * 第三步
       * 通过 characteristic2 写入OTA文件(每次写入20个bytes)
       */
      let count = 0
      const bytesArray = Array.from({ length: Math.ceil(data.btBytes.length / 20) }).map((_, index) => data.btBytes.slice(index * 20, (index + 1) * 20))
      for (const bytes of bytesArray) {
        const [error] = await writeValueWithResponse(characteristic2, bytes)
        if (error) {
          return reject("升级失败")
        }
        count++
        progressCallback((count / bytesArray.length * 100).toFixed(2))
      }

      /**
       * 第四步
       * OTA文件写入完成后，通过 characteristic1 写入 0x03 + OTA文件CRC32校验值(CRC32校验值为OTA文件最后4个byte(高低位转换))
       * 例：CRC32(0xAABBCCDD) 写入byte数组为：[0x03,0xDD,0xCC,0xBB,0xAA]
       */
      const [fourStepError] = await characteristic1Request([0x03, ...data.btBytes.slice(-4).reverse()])
      if (fourStepError) return reject("升级失败")

      resolve("升级完成")
    }).finally(() => {
      // 关闭通知
      characteristic1.stopNotifications()
    })
  }
}

export default new Bluetooth()

// const bluetooth = new Bluetooth()

// bluetooth.connect().then((result) => {
//   console.log(result)
// }).catch((error) => {
//   console.error(error)
// })

// const [error, bytes] = await getFileBytes(otafile.files[0])
// if (error) return
// otaFileData = getOTAFileFormat(bytes)
// console.log('OTAFile', otaFileData)

// bluetooth.upgrade(otaFileData, (progress) => {
//   console.log("进度", progress)
// }).then((result) => {
//   console.log(result)
// }).catch((error) => {
//   console.error(error)
// })
